package oneplay.YonghongBITools.Lineage;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.jackson.Jacksonized;
import lombok.extern.slf4j.Slf4j;
import oneplay.SqlScanner.core.lineage.BaseLineageParser;
import oneplay.SqlScanner.core.lineage.GBase.GBaseLineageParser;
import oneplay.SqlScanner.core.lineage.Misce.MisceLineageParser;
import oneplay.SqlScanner.core.lineage.TableInfo;
import oneplay.YonghongBITools.XMLDocument;
import oneplay.YonghongBITools.utils.JsonUtil;
import oneplay.YonghongBITools.utils.YHUtils;
import org.apache.commons.io.IOUtils;
import org.jdom2.Element;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Slf4j
public class YHLineageParser {
    private static final String UNKNOWN = "unknown"; // 未知

    private static final String TYPE_REPORT = "1"; // 报告
    //    private static final String TYPE_COMPONENT = "2"; // 组件
    private static final String TYPE_DATASET = "3"; // 数据集
    private static final String TYPE_TABLE = "4"; // 物理表
    private static final String TYPE_SQL = "5"; // 自定义SQL
    private static final String TYPE_DATASOURCE = "6"; // 数据源

    private static final String DATASET_TYPE_SQL = "16897"; // SQL数据集
    private static final String DATASET_TYPE_EXCEL = "16905"; // EXCEL数据集
    private static final String DATASET_TYPE_EMBED = "16902"; // 内嵌数据集数据集

    private static final Map<String, String> TYPE_MAPPING = new HashMap<String, String>() {{
        put(TYPE_REPORT, "永洪BI报告");
//        put(TYPE_COMPONENT, "永洪BI报告组件");
        put(TYPE_DATASET, "永洪BI数据集");
        put(DATASET_TYPE_EXCEL, "永洪BI数据集_Excel");
        put(DATASET_TYPE_EMBED, "永洪BI数据集_内嵌");
        put(TYPE_TABLE, "永洪BI物理表");
        put(TYPE_SQL, "永洪BI自定义SQL");
        put(TYPE_DATASOURCE, "永洪BI数据源");
    }};

    private final XMLDocument lineageDocument;
    private final String lineage;

    public YHLineageParser(XMLDocument lineageDocument) {
        this.lineageDocument = lineageDocument;
        lineage = JsonUtil.toString(parseReport());
    }

    public String getLineage() {
        return lineage;
    }

    private YHReportNode parseReport() {
        List<Element> elementList = getNodesByType(TYPE_REPORT);
        // 应该只有一个<type>1</type>的Node
        int size = elementList.size();
        if (size == 0) {
            log.error("Could not found '报告' node.");
            return null;
        } else {
            if (size > 1) {
                log.warn("More than one '报告' node found. Review needed.");
            }
            Element element = elementList.get(0);
            String name = element.getChildTextNormalize("name");
            String type = TYPE_MAPPING.get(TYPE_REPORT);
            String path = element.getChildTextNormalize("path");
            return new YHReportNode(name, type, path, parseDatasets());
        }
    }

    private List<YHDatasetNode> parseDatasets() {
        List<YHDatasetNode> datasetList = new ArrayList<>();
        List<Element> elementList = getNodesByType(TYPE_DATASET);
        if (elementList.isEmpty()) {
            log.error("Could not found '数据集' node.");
        } else {
            for (Element e : elementList) {
                String name = e.getChildTextNormalize("name");
                String path = e.getChildTextNormalize("path");
                String detailType = e.getChildTextNormalize("detailType");
                switch (detailType) {
                    case DATASET_TYPE_SQL:
                        datasetList.add(new YHDatasetNode(name, TYPE_MAPPING.get(TYPE_DATASET), path, parseSQLDatasetTables(e)));
                        break;
                    case DATASET_TYPE_EXCEL:
                    case DATASET_TYPE_EMBED:
                        datasetList.add(new YHDatasetNode(name, TYPE_MAPPING.get(detailType), path, null));
                        break;
                    default:
                        String detailTypeString = e.getChildTextNormalize("detailTypeString");
                        log.warn("New type dataset " + detailTypeString + "found. Review needed.");
                }
            }
        }
        return datasetList;
    }

    private List<Element> getNodesByType(String type) {
        return lineageDocument.getElements("/lineage/nodes/node[type=" + type + "]");
    }

    /**
     * 获取自定义SQL数据集用到的库表清单
     * 格式：数据源名.表名
     */
    private List<String> parseSQLDatasetTables(final Element element) {
        List<String> tableList = new ArrayList<>();
        // 自定义SQL数据集应只包含一条SQL语句
        String eId = element.getChildTextNormalize("id");
        String sqlDatasetNodeId = getFirstTargetIdBySourceId(eId);
        if (sqlDatasetNodeId == null) {
            String eName = element.getChildTextNormalize("name");
            log.error(eName + " lineage is incomplete. Manuel Refresh needed.");
            return tableList;
        }
        Element sqlDatasetNode = getFirstNodeById(sqlDatasetNodeId);
        String sql = YHUtils.parseYHSQL(sqlDatasetNode.getChildText("script"));

        // 获取数据源信息，一个数据集应当只有一个数据源
        String dataSourceNodeId = getFirstTargetIdBySourceId(sqlDatasetNodeId);
        String dataSourceName = UNKNOWN;
        String detailTypeString = UNKNOWN;
        if (dataSourceNodeId == null) {
            String sqlDatasetNodeName = sqlDatasetNode.getChildTextNormalize("name");
            log.error(sqlDatasetNodeName + " lineage is incomplete. Manuel Refresh needed.");
        } else {
            Element dataSourceNode = getFirstNodeById(dataSourceNodeId);
            dataSourceName = dataSourceNode.getChildTextNormalize("name");
            detailTypeString = dataSourceNode.getChildTextNormalize("detailTypeString");
        }
        List<String> tables = new ArrayList<>();
        // 解析SQL中依赖的表
        try {
            BaseLineageParser lineageParser;
            switch (detailTypeString.toLowerCase()) {
                case "generic":
                case "gbase":
                case "mysql":
                default:
                    lineageParser = new GBaseLineageParser(IOUtils.toInputStream(sql, StandardCharsets.UTF_8));
            }
            tables.addAll(lineageParser.getDepTables().stream().map(TableInfo::getTableName).collect(Collectors.toList()));
            if (tables.isEmpty()) {
                log.info("基于兜底逻辑判断");
                lineageParser = new MisceLineageParser(sql);
                tables.addAll(lineageParser.getDepTables().stream().map(TableInfo::getTableName).collect(Collectors.toList()));
            }
        } catch (IOException e) {
            log.error("Failed to get tables from sql", e);
        }
        for (String table : tables) {
            tableList.add(dataSourceName + "." + table);
        }
        return tableList;
    }

    private Element getFirstNodeById(String id) {
        return lineageDocument.getFirstElement("/lineage/nodes/node[id=" + id + "]");
    }

    private String getFirstTargetIdBySourceId(String sourceId) {
        return lineageDocument.getFirstElementText("/lineage/relations/relation[source=" + sourceId + "]/target");
    }

    @Getter
    @Setter
    @AllArgsConstructor
    @Builder
    @Jacksonized
    private static class YHReportNode {
        private String name;
        private String type;
        private String path;
        private List<YHDatasetNode> datasets;
    }

    @Getter
    @Setter
    @AllArgsConstructor
    @Builder
    @Jacksonized
    private static class YHDatasetNode {
        private String name;
        private String type;
        private String path;
        private List<String> tables;
    }

}
